use network_scanner_server_model::{Record, RecordEntry, RecordId, RecordType, RecordTypeId};
use rocket_db_pools::sqlx::{self, sqlite::SqliteRow, Row, Sqlite, Transaction};
use std::sync::Arc;

use super::{
    ip::{IpBinary, IpBinaryGetError},
    r#type::{TypeCache, LIST_ALL_CACHE, TYPE_CACHE},
};

pub enum InsertError {
    TypeNotFound(RecordTypeId),
    Database(sqlx::Error),
}

impl From<sqlx::Error> for InsertError {
    fn from(value: sqlx::Error) -> Self {
        InsertError::Database(value)
    }
}

pub async fn insert_record<'a>(
    db: &mut Transaction<'a, Sqlite>,
    record: RecordEntry,
) -> Result<(), InsertError> {
    let type_id = record.type_id;
    let cache: Option<Arc<TypeCache>> = {
        let statement_read_lock = TYPE_CACHE.read().await;
        statement_read_lock
            .as_ref()
            .expect("Type cache is not initialized")
            .get(&type_id)
            .cloned()
    };
    let cache = match cache {
        Some(cache) => cache,
        None => return Err(InsertError::TypeNotFound(type_id)),
    };

    sqlx::query(
        r#"
        INSERT INTO record (timestamp, type)
        VALUES (?, ?);
    "#,
    )
    .bind(record.timestamp)
    .bind(record.type_id.0)
    .execute(&mut **db)
    .await?;

    let record_id: RecordId = sqlx::query("SELECT LAST_INSERT_ROWID()")
        .map(|row: SqliteRow| row.get(0))
        .fetch_one(&mut **db)
        .await?;
    let mut type_statement = sqlx::query(cache.insert_type.as_str()).bind(record_id);

    let ip_addr: Vec<IpBinary> = record.ip_addr.into_iter().map(IpBinary::from).collect();
    for ip_addr in &ip_addr {
        type_statement = type_statement.bind(&ip_addr.0[..]);
    }
    for port in record.port {
        type_statement = type_statement.bind(port);
    }
    for str in record.str {
        type_statement = type_statement.bind(str);
    }
    for num in record.num {
        type_statement = type_statement.bind(num);
    }
    type_statement.execute(&mut **db).await?;

    Ok(())
}

#[allow(unused)]
pub enum ListRecordError {
    TypeNotFound(RecordTypeId),
    Database(sqlx::Error),
}

impl From<sqlx::Error> for ListRecordError {
    fn from(value: sqlx::Error) -> Self {
        ListRecordError::Database(value)
    }
}

fn map_record_row(r#type: &RecordType, row: SqliteRow) -> Result<Option<Record>, sqlx::Error> {
    let mut entry = RecordEntry {
        timestamp: row.try_get("timestamp")?,
        type_id: r#type.id,
        ip_addr: Vec::new(),
        port: Vec::new(),
        str: Vec::new(),
        num: Vec::new(),
    };
    let type_entry = &r#type.entry;
    for item in &type_entry.ip_addr {
        let ip_binary = IpBinary::try_get_from_row(&row, &format!("ip_{}", item.base.name));
        let ip_binary = match ip_binary {
            Ok(ip_binary) => ip_binary,
            Err(err) => match err {
                IpBinaryGetError::DataError(_) => return Ok(None),
                IpBinaryGetError::DatabaseError(err) => return Err(err),
            },
        };
        entry.ip_addr.push(ip_binary.into());
    }
    for item in &type_entry.port {
        entry
            .port
            .push(row.try_get(format!("port_{}", item.name).as_str())?);
    }
    for item in &type_entry.str {
        entry
            .str
            .push(row.try_get(format!("str_{}", item.name).as_str())?);
    }
    for item in &type_entry.num {
        entry
            .num
            .push(row.try_get(format!("num_{}", item.name).as_str())?);
    }
    Ok(Some(Record {
        id: row.try_get("id")?,
        entry,
    }))
}

pub async fn list_all_records<'a>(
    db: &mut Transaction<'a, Sqlite>,
    start: u32,
    len: u32,
) -> Result<Vec<Record>, ListRecordError> {
    let cache = TYPE_CACHE.read().await;
    let cache = cache.as_ref().expect("Cache is not initialized");

    let statement = LIST_ALL_CACHE.read().await;
    let statement = statement.as_ref().expect("Cache is not initialized");

    Ok(sqlx::query(statement.as_str())
        .bind(start)
        .bind(len)
        .try_map(|row: SqliteRow| {
            let type_id = RecordTypeId(row.try_get("type")?);
            let r#type = match cache.get(&type_id) {
                Some(entry) => &entry.r#type,
                None => return Ok(None),
            };

            map_record_row(r#type, row)
        })
        .fetch_all(&mut **db)
        .await?
        .into_iter()
        .flatten()
        .collect())
}

pub async fn list_records<'a>(
    db: &mut Transaction<'a, Sqlite>,
    type_id: RecordTypeId,
    start: u32,
    len: u32,
) -> Result<Vec<Record>, ListRecordError> {
    let cache = TYPE_CACHE.read().await;
    let cache = cache.as_ref().expect("Cache is not initialized");
    let cache = cache.get(&type_id);
    let cache = match cache {
        Some(cache) => cache.clone(),
        None => return Err(ListRecordError::TypeNotFound(type_id)),
    };
    let r#type = &cache.r#type;
    let list = &cache.list;

    Ok(sqlx::query(list.as_str())
        .bind(start)
        .bind(len)
        .try_map(|row: SqliteRow| map_record_row(r#type, row))
        .fetch_all(&mut **db)
        .await?
        .into_iter()
        .flatten()
        .collect())
}
